
/* 
 * Copyright (c) 2008 Michael Eddington
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy  
 * of this software and associated documentation files (the "Software"), to deal 
 * in the Software without restriction, including without limitation the rights  
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell  
 * copies of the Software, and to permit persons to whom the Software is  
 * furnished to do so, subject to the following conditions: 
 * 
 * The above copyright notice and this permission notice shall be included in  
 * all copies or substantial portions of the Software. 
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE  
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 * SOFTWARE. 
 * 
 * Authors:
 *   Michael Eddington (meddington@phed.org)
 *
 * $Id$
 */

#include <stdio.h>
#include <string.h>
#include <validate.h>

// Remove any NULL chars that may get in the way :)
char* RemoveNullCharacters(unsigned char* s, unsigned int length)
{
	unsigned int sPos = 0;
	unsigned int newSPos = 0;

	char* newS = new char[strlen(s)+1];
	memset(newS, 0, length);

	for(; sPos < length; sPos++, newSPos++)
	{
		if(s[sPos] == 0)
			newSPos--;
		else
			newS[newSPos] = s[sPos];
	}

	return newS;
}

// Determin if this string is "dangerous" for XSS
// Performs the asp.net 2.0 way of things.
BOOL IsDangerousString2(const char* s, int* matchIndex)
{
	*matchIndex = 0;
	
	unsigned int startIndex = 0;
	unsigned int num2;
	char ch;

	while (true)
	{
		
		num2 = strchar(s+startIndex, '<');
		if(!num2)
			num2 = strchar(s+startIndex, '&');
		
		// If not found return false
		if (num2 == 0)
			return false;
		
		// Calculate index
		num2 -= s+startIndex;

		// If last character in string return false
		if (num2 == (strlen(s) - 1))
		{
			return false;
		}
		
		matchIndex = num2;
		
		// If '<' is followed by alpha, !, or / return true
		if ((s[num2] == '<') 
			&& (
				isalpha(s[(num2 + 1)]
				|| (s[(num2 + 1)] == '!') 
				|| (s[(num2 + 1)] == '/')
				) )
		{
			return true;
		}
		// If &# then return true
		else if (s[num2] == '&' && s[(num2 + 1)] == '#')
		{
			return true;
		}

		// Next!
		startIndex = (num2 + 1);
	}
}

// Return the index of any character in a char array (ended will NULL).
// Returns -1 when not found or startIndex is larger then size of s
unsigned int IndexOfAny(const char* s, const char* startingChars, startIndex)
{
	char* c = 0;

	// Lets not skip too far ahead :)
	if(strlen(s) >= startIndex)
		return -1;

	// Skip ahead
	s += startIndex;
	
	for(int i = 0; i < strlen(startingChars); i++)
		if( (c = strchr(s, startingChars[i]) )
			return c - s;
	
	// Not found
	return -1;
}


BOOL IsDangerousExpressionString(char*, unsigned index)
{
	if ((index + 10) >= strlen(s))
	{
		return false;
	}
	if ((s[(index + 1)] != 'x') && (s[(index + 1)] != 'X'))
	{
		return false;
	}

	return (String::Compare(s, (index + 2), S"pression(", 0, 9, true, CultureInfo::InvariantCulture) == 0);
}

BOOL IsDangerousOnString(char*, unsigned int index)
{
	if (tolower(s[(index + 1)]) != 'n')
	{
		return false;
	}

	if ((index > 0) && isalpha(s[(index - 1)]))
	{
		return false;
	}

	unsigned int length = strlen(s);
	index += 2;

	while (((index < length) && isalpha(s[index])))
	{
		index++;
	}
	while (((index < length) && isspace(s[index])))
	{
		index++;
	}
	return ((index < length) && (s[index] == '='));
}

// Determin if we have script:
BOOL IsDangerousScriptString(char*, unsigned index)
{
	unsigned int length = strlen(s);
	
	if ((index + 6) >= length)
		return false;
	
	if (   tolower(s[(index + 1)]) != 'c' 
		|| tolower(s[(index + 2)]) != 'r' 
		|| tolower(s[(index + 3)]) != 'i' 
		|| tolower(s[(index + 4)]) != 'p'
		|| tolower(s[(index + 5)]) != 't') 
	{
		return false;
	}
	
	index += 6;
	while (((index < length) && isspace(s[index])))
	{
		index++;
	}
	
	return ((index < length) && (s[index] == ':'));
}

// The dangerous starting chars for check version 1
static char* startingChars1 = { '<', '&', 'o', 'O', 's', 'S', 'e', 'E', '\0' };

BOOL IsDangerousString1(const char* s, unsigned int* matchIndex)
{
	matchIndex = 0;
	unsigned int startIndex = 0;
	unsigned int index = 0;

	while (true)
	{
		index = IndexOfAny(s, startingChars1, startIndex);

		if (index < 0)
			return false;

		if (index == (strlen(s) - 1))
			return false;

		*matchIndex = index;
		switch (tolower(s[index]))
		{
		case 'e':
				if (IsDangerousExpressionString(s, index))
					return true;

				break;

		case 'o':
				if (IsDangerousOnString(s, index))
					return true;

				break;

		case '&':
				if (s[(index + 1)] == '#')
					return true;

				break;

		case '<':
				if ( isalpha(s[index + 1]) || s[(index + 1)] == '!' || s[(index + 1)] == '/' )
					return true;

				break;

		case 's':
				if (IsDangerousScriptString(s, index))
					return true;

				break;

		}
		startIndex = (index + 1);
	}
}

// /////////////////////////////////////////////////////////////////

BOOL ValidateString1(const char* s, const unsigned int length)
{
    unsigned int matchedIndex;
    
	s = RemoveNullCharacters(s, strlen(length));
    return IsDangerousString1(s, &matchedIndex);
}

BOOL ValidateString2(const char* s, const unsigned int length)
{
    unsigned int matchedIndex;
    
	s = RemoveNullCharacters(s, strlen(length));
    return IsDangerousString2(s, &matchedIndex);
}

// end
